package generate

import (
	"errors"
	"fmt"

	"github.com/zhufuyi/sponge/pkg/replacer"

	"github.com/spf13/cobra"
)

// CacheCommand generate cache code
func CacheCommand(parentName string) *cobra.Command {
	var (
		moduleName string // module name for go.mod
		outPath    string // output directory
		cacheName  string // cache name
		prefixKey  string // prefix key
		keyName    string // key name
		keyType    string // key type
		valueName  string // value name
		valueType  string // value type
	)

	cmd := &cobra.Command{
		Use:   "cache",
		Short: "Generate cache code",
		Long: fmt.Sprintf(`generate cache code.

Examples:
  # generate kv cache code
  sponge %s cache --module-name=yourModuleName --cache-name=userToken --prefix-key=user:token: --key-name=id --key-type=uint64 --value-name=token --value-type=string

  # generate kv cache code and specify the server directory, Note: code generation will be canceled when the latest generated file already exists.
  sponge %s cache --module-name=yourModuleName --cache-name=token --prefix-key=user:token: --key-name=id --key-type=uint64 --value-name=token --value-type=string --out=./yourServerDir
`, parentName, parentName),
		SilenceErrors: true,
		SilenceUsage:  true,
		RunE: func(cmd *cobra.Command, args []string) error {
			mdName, _ := getNamesFromOutDir(outPath)
			if mdName != "" {
				moduleName = mdName
			} else if moduleName == "" {
				return errors.New(`required flag(s) "module-name" not set, use "sponge micro cache -h" for help`)
			}

			var err error
			outPath, err = runCacheCommand(moduleName, cacheName, prefixKey, keyName, keyType, valueName, valueType, outPath)
			if err != nil {
				return err
			}

			fmt.Printf(`
using help:
  move the folder "internal" to your project code folder.

`)
			fmt.Printf("generate \"cache\" code successfully, out = %s\n", outPath)
			return nil
		},
	}

	cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
	cmd.Flags().StringVarP(&cacheName, "cache-name", "c", "", "cache name, e.g. userToken")
	_ = cmd.MarkFlagRequired("cache-name")
	cmd.Flags().StringVarP(&prefixKey, "prefix-key", "p", "", "cache prefix key, must end with a colon, e.g. user:token:")
	cmd.Flags().StringVarP(&keyName, "key-name", "", "", "key name, e.g. id")
	_ = cmd.MarkFlagRequired("key-name")
	cmd.Flags().StringVarP(&keyType, "key-type", "", "", "key go type, e.g. uint64")
	_ = cmd.MarkFlagRequired("key-type")
	cmd.Flags().StringVarP(&valueName, "value-name", "", "", "value name, e.g. token")
	_ = cmd.MarkFlagRequired("value-name")
	cmd.Flags().StringVarP(&valueType, "value-type", "", "", "value go type, e.g. string")
	_ = cmd.MarkFlagRequired("value-type")
	cmd.Flags().StringVarP(&outPath, "out", "o", "", "output directory, default is ./cache_<time>,"+
		" if you specify the directory where the web or microservice generated by sponge, the module-name flag can be ignored")

	return cmd
}

func runCacheCommand(moduleName string, cacheName string, prefixKey string, keyName string,
	keyType string, valueName string, valueType string, outPath string) (string, error) {
	subTplName := "cache"
	r := Replacers[TplNameSponge]
	if r == nil {
		return "", errors.New("replacer is nil")
	}

	if prefixKey == "" || prefixKey == ":" {
		prefixKey = cacheName + ":"
	} else if prefixKey[len(prefixKey)-1] != ':' {
		prefixKey += ":"
	}

	// setting up template information
	subDirs := []string{ // only the specified subdirectory is processed, if empty or no subdirectory is specified, it means all files
		"internal/cache",
	}
	ignoreDirs := []string{} // specify the directory in the subdirectory where processing is ignored
	ignoreFiles := []string{ // specify the files in the subdirectory to be ignored for processing
		"doc.go",
		"userExample.go",
		"userExample_test.go",
	}

	r.SetSubDirsAndFiles(subDirs)
	r.SetIgnoreSubDirs(ignoreDirs...)
	r.SetIgnoreSubFiles(ignoreFiles...)
	fields := addCacheFields(moduleName, r, cacheName, prefixKey, keyName, keyType, valueName, valueType)
	r.SetReplacementFields(fields)
	_ = r.SetOutputDir(outPath, subTplName)
	if err := r.SaveFiles(); err != nil {
		return "", err
	}

	return r.GetOutputDir(), nil
}

func addCacheFields(moduleName string, r replacer.Replacer, cacheName string, prefixKey string, keyName string,
	keyType string, valueName string, valueType string) []replacer.Field {
	var fields []replacer.Field
	fields = append(fields, deleteFieldsMark(r, cacheFile, startMark, endMark)...)

	// match the case where the value type is a pointer
	if valueType[0] == '*' {
		fields = append(fields, []replacer.Field{
			{
				Old:             "var valueNameExample valueTypeExample",
				New:             fmt.Sprintf("%s := &%s{}", valueName, valueType[1:]),
				IsCaseSensitive: false,
			},
			{
				Old:             "&valueNameExample",
				New:             valueName,
				IsCaseSensitive: false,
			},
		}...)
	}

	fields = append(fields, []replacer.Field{
		{
			Old: "github.com/zhufuyi/sponge/internal/model",
			New: moduleName + "/internal/model",
		},
		{
			Old:             "cacheNameExample",
			New:             cacheName,
			IsCaseSensitive: true,
		},
		{
			Old:             "prefixKeyExample:",
			New:             prefixKey,
			IsCaseSensitive: false,
		},
		{
			Old:             "keyNameExample",
			New:             keyName,
			IsCaseSensitive: false,
		},
		{
			Old:             "keyTypeExample",
			New:             keyType,
			IsCaseSensitive: false,
		},
		{
			Old:             "valueNameExample",
			New:             valueName,
			IsCaseSensitive: false,
		},
		{
			Old:             "valueTypeExample",
			New:             valueType,
			IsCaseSensitive: false,
		},
	}...)

	return fields
}
